home *** CD-ROM | disk | FTP | other *** search
- /*
-
- PPRD
-
- Line printer daemon using Berkeley LPD protocol on top of WATTCP for up
- to 3 parallel printer ports.
-
- This daemon is written as a state machine because it has to support
- several connections on a single threaded system, DOS.
-
- When started up, it checks to see how many printers are known to the
- BIOS. These printers are served as LPT[123]. It listens on the
- standard LPD port 515, but this can be changed from the command line.
-
- Examples of options:
-
- -p1515 listen on port 1515 instead
- -j9200 use direct protocol instead at port 9100 (defaults to 9100 if unspec)
- -23 disable printers 2 and 3
- -n2 two printers, no matter what BIOS claims
- -b12 on 1 and 2 bypass BIOS and send directly to port (parallel ports only)
- -t don't indicate available printers with tones
- -i reinitialise printer via hardware line on job abort
- -s disable subnet match (server and client must be on same subnet)
- -alist comma separated list of up to 20 domain names allowed connection
- -dlist comma separated list of up to 20 domain names denied connection
- last two are mutually exclusive and independent of subnet check
- -lhost log diagnostics to host of that domain name
-
- Jobs can be aborted by C-F1 through C-F3 for that printer.
-
- The LPD protocol is summarised below where S = server and C = client.
- Ack = '\0' and nak = '\001'.
-
- C: \002printer\n
- S: ack/nak
- C: \003size datafilename\n (size is bytes as decimal string)
- S: ack/nak
- C: size bytes of data + one '\0' byte
- S: ack/nak
- C: \002size cntlfilename\n
- S: ack/nak
- C: size bytes of control + one '\0' byte
- S: ack/nak
-
- The data and control files can occur in either order. In this
- implementation the filenames are ignored.
-
- This program was compiled under Borland C++ 3.1 in C mode for the small
- model. You will require the WATTCP libraries. A copy is included in the
- distribution. For the sources of WATTCP, ask archie where the archives
- are.
-
- Please send bug fixes, ports and enhancements to the author for
- incorporation in newer versions.
-
- Copyright (C) 1995 Ken Yap (ken@syd.dit.csiro.au)
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <memory.h>
- #include <sys/types.h>
- #include <time.h>
-
- #include <tcp.h>
-
- #include <dos.h>
- #include <bios.h>
- #include <conio.h>
-
- #include "pprd.h"
-
- #define PROGRAM "PPRD"
- #define VERSION "Version 1.00 15 Jul 1995"
- #define AUTHOR "Copyright (C) 1995 Ken Yap (ken@syd.dit.csiro.au)"
-
- #define MAXLPT 3 /* supported under DOS */
- #ifdef unix /* test implementation */
- #define MAXCON 1
- #else
- /* for lpr, 1 for lpq, and direct */
- #define MAXCON (MAXLPT+1)
- #endif
- /* try this many times to see if can accept next byte right away */
- #define POLL_MAX 10
-
- unsigned int lpdport = LPDPORT;
- unsigned int jdport = JDPORT;
- int jdprotocol = 0; /* use direct protocol if != 0 */
- int nlpt = 0;
- struct lpt_info lpt[MAXLPT];
- int notone = 0;
- int reinit = 0; /* reinitialise on abort */
- unsigned int lpt_tone[MAXLPT] = { 523, 587, 659 }; /* CDE */
- char lpt_names[MAXLPT][16] = { "lpt1", "lpt2", "lpt3" };
- struct conn_info conn[MAXCON]; /* one extra for lpq/lprm requests */
- struct conn_info log; /* for connection to syslogd */
- char *loghost = 0;
- int check_subnet = 1;
- int nallow = 0, ndeny = 0;
- longword allow[20], deny[20];
- void (*normal_init)(char *name, char *value);
-
- #define MAXALLOW (sizeof(allow)/sizeof(allow[0]))
- #define MAXDENY (sizeof(deny)/sizeof(deny[0]))
-
- extern longword my_ip_addr;
- extern longword sin_mask;
-
- #define ack_cmd(c) (void)sock_fastwrite(&c->sock, (byte *)"", 1)
- #define nak_cmd(c) (void)sock_fastwrite(&c->sock, (byte *)"\001", 1)
-
- char *ptime(void)
- {
- time_t t;
- char *p;
-
- t = time(0);
- p = ctime(&t);
- p[24] = '\0';
- return (p);
- }
-
- void init_queues(void)
- {
- struct conn_info *c;
- int i, ret;
- struct lpt_info *p;
-
- if (nlpt == 0) /* not overridden by -n? */
- {
- ret = biosequip();
- nlpt = (ret >> 14) & 0x3;
- }
- for (i = 0; i < nlpt; ++i)
- {
- p = &lpt[i];
- if (p->avail == DISABLED) /* unavail from cmd line? */
- continue;
- (void)bios_printer_init(i); /* initialise printer */
- sleep(2); /* let printer settle */
- p->status = bios_printer_status(i); /* get status */
- if (p->status & (P_TIMEOUT|P_IOERROR))
- {
- p->avail = NONESUCH; /* printer not available */
- (void)printf("%s: Error initialising %s\n",
- ptime(), lpt_names[i]);
- }
- else
- {
- p->avail = FREE; /* printer available */
- (void)printf("%s: %s initialised",
- ptime(), lpt_names[i]);
- if (p->hwaddr && (ret = peek(0, 0x408 + i * 2)) != 0)
- (void)printf(", direct hardware access at %#3X",
- p->hwaddr = ret);
- (void)printf("\n");
- }
- }
- for (i = 0; i < MAXLPT; ++i)
- {
- if (notone || lpt[i].avail != FREE)
- continue;
- sound(lpt_tone[i]);
- sleep(1);
- nosound();
- }
- for (i = 0; i < MAXCON; ++i)
- {
- c = &conn[i];
- c->state = INIT; /* initial connection state */
- c->controlfirst = 0;
- c->printer = -1;
- }
- }
-
- void init_log(void)
- {
- longword logip;
-
- if (loghost == 0 ||
- (logip = resolve(loghost)) == (longword)0 ||
- udp_open(&log.sock, 0, logip, LOGPORT, NULL) == 0)
- {
- loghost = 0;
- return;
- }
- (void)sprintf(log.buffer, LOG_TAG PROGRAM " " VERSION
- ", %d printer(s)\n", nlpt);
- (void)sock_fastwrite(&log.sock, log.buffer, strlen(log.buffer));
- }
-
- void report_change(char *msg, int printer)
- {
- (void)printf("%s: %s %s\n", ptime(), lpt_names[printer], msg);
- if (loghost != 0) /* log to syslogd */
- {
- (void)sprintf(log.buffer, LOG_TAG "%s %s\n", lpt_names[printer], msg);
- (void)sock_fastwrite(&log.sock, log.buffer, strlen(log.buffer));
- }
- }
-
- void lpt_status_change(int i, int delta, int status)
- {
- if (status & P_TIMEOUT)
- report_change("time out", i);
- if (status & P_IOERROR)
- report_change("I/O error", i);
- if (delta & P_SELECTED)
- {
- if (status & P_SELECTED)
- report_change("online", i);
- else
- report_change("offline", i);
- }
- if (status & P_NOPAPER)
- report_change("paper out", i);
- }
-
- int same_subnet(longword client)
- {
- /* assumes that bit operations can be done on longword */
- return (((client ^ my_ip_addr) & sin_mask) == 0);
- }
-
- int in_list(longword ip, longword table[], int entries, int maxtable)
- {
- int i;
-
- for (i = 0; i < entries && i < maxtable; ++i)
- if (ip == table[i])
- return (1);
- return (0);
- }
-
- int check_access(longword ip)
- {
- if (check_subnet && !same_subnet(ip))
- return (0);
- if (nallow > 0)
- return (in_list(ip, allow, nallow, MAXALLOW) ? 1 : 0);
- if (ndeny > 0)
- return (in_list(ip, deny, ndeny, MAXDENY) ? 0 : 1);
- return (1);
- }
-
- /*
- * Translate from name to printer number
- */
- int printernumber(char *printername)
- {
- int i;
-
- for (i = 0; i < MAXLPT; ++i)
- if (stricmp(lpt_names[i], printername) == 0)
- return (i);
- return (-1);
- }
-
- void queuename(struct conn_info *c)
- {
- int i, current, delta;
- char printername[sizeof(lpt_names[0])];
-
- *c->bufip = '\0';
- if (*c->buffer != '\002' && *c->buffer != '\003')
- {
- c->state = NAKANDCLOSE;
- return;
- }
- if (sscanf(c->buffer+1, "%15s", printername) != 1 ||
- (i = printernumber(printername)) < 0)
- {
- (void)printf("%s: Printer name error: %s", ptime(), c->buffer+1);
- c->state = NAKANDCLOSE;
- return;
- }
- switch (*c->buffer)
- {
- case '\002':
- if (lpt[i].avail != FREE)
- {
- (void)printf("%s: Printer not available: %s",
- ptime(), c->buffer+1);
- c->state = NAKANDCLOSE;
- return;
- }
- c->printer = i;
- lpt[i].avail = BUSY;
- (void)printf("%s: Job for %s started\n",
- ptime(), lpt_names[i]);
- c->state = RECVJOB;
- ack_cmd(c);
- break;
- case '\003':
- case '\004':
- (void)sprintf(c->buffer, "%s is ", lpt_names[i]);
- switch (lpt[i].avail)
- {
- case BUSY:
- (void)strcat(c->buffer, "busy\n");
- break;
- case FREE:
- (void)strcat(c->buffer, "available");
- /* get status of printer and save it */
- lpt[i].status = current = bios_printer_status(i);
- (void)strcat(c->buffer, current & P_SELECTED ?
- " online" : " offline");
- if (current & P_NOPAPER)
- (void)strcat(c->buffer, " no paper");
- (void)strcat(c->buffer, "\n");
- break;
- default:
- (void)strcat(c->buffer, "not available\n");
- break;
- }
- (void)sock_fastwrite(&c->sock, c->buffer, strlen(c->buffer));
- c->state = CLOSING;
- break;
- }
- }
-
- void get_jobinfo(struct conn_info *c)
- {
- char *p;
-
- p = c->bufip;
- if (buffer_room(c) == 0)
- --p;
- *p = '\0';
- for (p = strtok(c->buffer, "\n"); p != 0; p = strtok(0, "\n"))
- {
- switch (*p)
- {
- case 'N':
- (void)strncat(c->jobname, p+1, sizeof(c->jobname)-1);
- break;
- case 'P':
- (void)strncat(c->username, p+1, sizeof(c->username)-1);
- break;
- case 'H':
- (void)strncat(c->hostname, p+1, sizeof(c->hostname)-1);
- break;
- }
- }
- }
-
- void read_bytes(struct conn_info *c)
- {
- int count;
- char *p;
-
- if ((count = sock_rbused(&c->sock)) > 0)
- {
- if (count > buffer_room(c))
- count = buffer_room(c);
- (void)sock_fastread(&c->sock, (byte *)c->bufip, count);
- c->bufip += count;
- }
- if (!data_unseen(c))
- return;
- nextstate:
- switch (c->state)
- {
- case QUEUENAME:
- queuename(c);
- reset_ptrs(c);
- break;
- case RECVJOB:
- switch (*c->buffer)
- {
- case '\001':
- ack_cmd(c);
- c->state = CLOSING;
- break;
- case '\002':
- c->state = CONTROLINFO;
- c->controlfirst = 1;
- goto nextstate;
- case '\003':
- c->state = DATAINFO;
- goto nextstate;
- }
- case CONTROLINFO:
- case DATAINFO:
- p = c->bufip;
- if (buffer_room(c) == 0)
- --p;
- *p = '\0';
- if (sscanf(c->buffer+1, "%ld", &c->bytelen) != 1)
- {
- (void)printf("%s: %s, error parsing %s",
- ptime(), lpt_names[c->printer], c->buffer+1);
- c->state = NAKANDCLOSE;
- return;
- }
- ++c->bytelen; /* for EOF byte */
- ack_cmd(c);
- c->jobname[0] = '\0';
- c->username[0] = '\0';
- c->hostname[0] = '\0';
- c->state = c->state == CONTROLINFO ? CONTROL : DATA;
- if (c->state == DATA)
- c->starttime = time(0);
- reset_ptrs(c);
- break;
- case CONTROL:
- /* copy out interesting information from buffer */
- get_jobinfo(c);
- c->bytelen -= count;
- if (c->bytelen <= 0)
- {
- (void)printf("%s: Job ", ptime());
- if (c->jobname[0] != '\0')
- (void)printf("%s ", c->jobname);
- if (c->username[0] != '\0' && c->hostname[0] != '\0')
- (void)printf("for %s@%s ", c->username,
- c->hostname);
- (void)printf("on %s\n", lpt_names[c->printer]);
- ack_cmd(c);
- c->state = c->controlfirst ? DATAINFO : CLOSING;
- }
- reset_ptrs(c);
- break;
- case DATA:
- c->bytelen -= count;
- if (c->bytelen <= 0 && count > 0) /* skip EOF byte */
- --c->bufip;
- c->state = PRINTING;
- break;
- }
- }
-
- /*
- * Direct hardware write to printer port
- */
- int printer_outbuf(int port, char *buffer, int count)
- {
- int i, printed, status;
-
- printed = 0;
- inportb(port+1); /* read status */
- status = (inportb(port+1) & 0xf8) ^ 0x48;
- while (count > 0 && (status & P_READY) == P_READY)
- {
- outportb(port, *buffer++); /* write character */
- outportb(port+2, 0x0d); /* raise strobe */
- outportb(port+2, 0x0c); /* lower strobe */
- ++printed;
- --count;
- /* sample the busy line for a few tries */
- for (i = 0; i < POLL_MAX; ++i)
- {
- status = (inportb(port+1) & 0xf8) ^ 0x48;
- if ((status & P_READY) == P_READY)
- break;
- }
- }
- return (printed);
- }
-
- void print_data(struct conn_info *c)
- {
- int printer, current, delta, printed;
- struct lpt_info *p;
-
- p = &lpt[printer = c->printer];
- current = bios_printer_status(printer);
- /* report changes from last time */
- delta = (current ^ p->status) & P_CHANGES;
- if (delta)
- lpt_status_change(printer, delta, current);
- p->status = current;
- /* if printer is busy, try to collect more data */
- if ((current & P_READY) != P_READY && buffer_room(c) > 0)
- {
- c->state = DATA;
- return;
- }
- if (p->hwaddr != 0)
- {
- printed = printer_outbuf(p->hwaddr, c->bufop,
- c->bufip - c->bufop);
- c->bufop += printed;
- }
- else
- {
- printed = 0;
- /* loop, printing as much as possible */
- /* eat your heart out, Pascal */
- while (c->bufop < c->bufip &&
- ((current & P_READY) == P_READY ||
- (bios_printer_status(printer) & P_READY) == P_READY))
- {
- current = bios_printer_outch(printer, *c->bufop++);
- ++printed;
- }
- }
- if (!data_remaining(c))
- {
- reset_ptrs(c);
- /* in jdprotocol bytelen will not be 0 before EOF
- well, not before maxlong bytes anyway */
- if (c->bytelen > 0)
- c->state = DATA;
- else
- {
- ack_cmd(c);
- c->state = c->controlfirst ? CLOSING : CONTROLINFO;
- }
- }
- c->joblen += printed;
- }
-
- void show_stats(struct conn_info *c)
- {
- time_t elapsed;
-
- elapsed = time(0) - c->starttime;
- (void)printf("%s: %s: %ld bytes %ld seconds", ptime(),
- lpt_names[c->printer], c->joblen, elapsed);
- if (elapsed > 0)
- (void)printf(" %ld bytes/second", c->joblen / elapsed);
- (void)printf("\n");
- }
-
- void loop(int port, struct conn_info *c)
- {
- unsigned int cport;
- struct sockaddr client;
- int i;
- char *name;
-
- switch (c->state) {
- case INIT:
- if (jdprotocol)
- {
- if (port >= MAXLPT || lpt[port].avail != FREE)
- break; /* don't offer connection */
- cport = jdport + port;
- c->printer = port;
- }
- else
- cport = lpdport;
- tcp_listen(&c->sock, cport, 0L, 0, NULL, 0);
- (void)printf("%s: Connection %d listening on TCP port %u\n",
- ptime(), port, cport);
- c->state = WAITING;
- break;
- case WAITING:
- tcp_tick(NULL);
- if (!sock_established(&c->sock))
- break;
- i = sizeof(client);
- client.s_ip = 0;
- name = getpeername(&c->sock, &client, &i) == 0 ?
- inet_ntoa(c->buffer, client.s_ip) : "?";
- if (check_access(client.s_ip))
- {
- (void)printf("%s: Connection %d from %s\n",
- ptime(), port, name);
- c->state = jdprotocol ? DATA : QUEUENAME;
- c->bytelen = 0x7ffffff; /* maxlong */
- c->starttime = time(0);
- c->joblen = 0L;
- }
- else
- {
- (void)printf("%s: Connection %d from %s refused\n",
- ptime(), port, name);
- c->state = CLOSING;
- }
- reset_ptrs(c);
- break;
- case QUEUENAME:
- case RECVJOB:
- case CONTROLINFO:
- case CONTROL:
- case DATAINFO:
- case DATA:
- if (tcp_tick(&c->sock))
- read_bytes(c);
- /* don't shutdown until all printed */
- else if (data_remaining(c))
- c->state = PRINTING;
- else
- c->state = CLOSING;
- break;
- case PRINTING:
- if (tcp_tick(&c->sock) || data_remaining(c))
- print_data(c);
- else
- c->state = CLOSING;
- break;
- case NAKANDCLOSE:
- nak_cmd(c);
- case CLOSING:
- show_stats(c);
- sock_flush(&c->sock);
- sock_close(&c->sock);
- reinit:
- /* free up printer */
- if (0 <= c->printer && c->printer < MAXLPT
- && lpt[c->printer].avail == BUSY)
- lpt[c->printer].avail = FREE;
- c->state = INIT;
- c->controlfirst = 0;
- c->printer = -1;
- break;
- case ABORT:
- sock_abort(&c->sock);
- goto reinit;
- }
- }
-
- void check_key(void)
- {
- int key, i;
-
- if (((key = bioskey(0)) & 0xff) != 0)
- return; /* ASCII key */
- key = (key >> 8) & 0xff;
- if (key < CF1 || key > CF3)
- return;
- key -= CF1; /* which printer? */
- for (i = 0; i < MAXCON; ++i)
- {
- if (conn[i].printer == key && conn[i].state != INIT)
- {
- if (reinit)
- {
- (void)printf("%s: Aborting job and reinitialising %s\n",
- ptime(), lpt_names[key]);
- (void)biosprint(1, 0, key);
- sleep(2); /* let printer settle */
- }
- else
- {
- (void)printf("%s: Aborting job on %s\n",
- ptime(), lpt_names[key]);
- }
- conn[i].state = ABORT; /* abort job */
- return;
- }
- }
- }
-
- void make_list(char *adjective, char *s,
- longword table[], int *nentries, int maxtable)
- {
- char *p;
- longword ip;
- int i;
- char buffer[64];
-
- for ( ; *s != '\0'; s = p + 1)
- {
- if ((p = strchr(s, ',')) == 0)
- p = s + strlen(s) - 1;
- else
- *p = '\0'; /* mark end */
- if ((ip = resolve(s)) == (longword)0)
- continue;
- if (*nentries >= maxtable)
- continue; /* should print warning */
- table[(*nentries)++] = ip;
- }
- for (i = 0; i < *nentries; ++i)
- (void)printf("%s ", inet_ntoa(buffer, table[i]));
- if (*nentries > 0)
- (void)printf("%s access\n", adjective);
- }
-
- void my_init(char *name, char *value)
- {
- if (strcmp(name, "PRINTER1NAME") == 0)
- {
- strncpy(lpt_names[0], value, sizeof(lpt_names[0]));
- lpt_names[0][sizeof(lpt_names[0])-1] = '\0';
- }
- else if (strcmp(name, "PRINTER2NAME") == 0)
- {
- strncpy(lpt_names[1], value, sizeof(lpt_names[1]));
- lpt_names[1][sizeof(lpt_names[1])-1] = '\0';
- }
- else if (strcmp(name, "PRINTER3NAME") == 0)
- {
- strncpy(lpt_names[2], value, sizeof(lpt_names[2]));
- lpt_names[2][sizeof(lpt_names[2])-1] = '\0';
- }
- else if (normal_init)
- (*normal_init)(name, value);
- }
-
- void options(int argc, char **argv)
- {
- char *s;
- int i;
-
- for (i = 0; i < MAXLPT; ++i)
- lpt[i].hwaddr = 0;
- for (--argc, ++argv; argc > 0 && *argv[0] == '-'; --argc, ++argv)
- {
- s = argv[0];
- switch (*++s)
- {
- case '1': case '2': case '3':
- for ( ; '1' <= *s && *s <= '3'; ++s)
- lpt[*s - '1'].avail = DISABLED;
- break;
- case 'a':
- if (ndeny <= 0)
- make_list("allowed", ++s, allow, &nallow,
- MAXALLOW);
- break;
- case 'b':
- ++s;
- for ( ; '1' <= *s && *s <= '3'; ++s)
- lpt[*s - '1'].hwaddr = 1;
- break;
- case 'd':
- if (nallow <= 0)
- make_list("denied", ++s, deny, &ndeny,
- MAXDENY);
- break;
- case 'i':
- reinit = 1;
- break;
- case 'j':
- jdprotocol = 1;
- if ((jdport = atoi(++s)) <= 0)
- jdport = JDPORT;
- break;
- case 'l':
- loghost = ++s;
- break;
- case 'n':
- if ((nlpt = atoi(++s)) > MAXLPT || nlpt < 0)
- nlpt = 0;
- break;
- case 'p': /* alternate TCP port */
- if ((lpdport = atoi(++s)) <= 0)
- lpdport = LPDPORT;
- break;
- case 's':
- check_subnet = 0;
- break;
- case 't':
- notone = 1;
- break;
- }
- }
- }
-
- int main(int argc, char **argv)
- {
- int i;
-
- (void)printf(PROGRAM " " VERSION " " AUTHOR "\n");
- (void)printf(PROGRAM " comes with ABSOLUTELY NO WARRANTY; for details read the file COPYING\n");
- (void)printf("This is free software, and you are welcome to redistribute it\n");
- (void)printf(" under certain conditions; see the file COPYING for details.\n");
- (void)printf("C-F1 through C-F3 to abort printer jobs\n");
- (void)memset(conn, 0, sizeof(conn));
- (void)memset(lpt, 0, sizeof(lpt));
- tzset();
- options(argc, argv);
- /* hook onto init procedure to get NAME=VALUE pairs */
- normal_init = usr_init;
- usr_init = my_init;
- dbuginit();
- sock_init();
- init_queues();
- if (nlpt == 0)
- {
- (void)printf("%s: No printers connected\n", ptime());
- return (1);
- }
- init_log();
- for (;;)
- {
- for (i = 0; i < MAXCON; ++i)
- loop(i, &conn[i]);
- if (kbhit())
- check_key();
- }
- /*NOTREACHED*/
- return (0);
- }
-